#!/usr/bin/perl
#===============================================================================
#
#          FILE:  pundit.pl
#
#         USAGE:  ./pundit.pl
#
#   DESCRIPTION:  This is the backend script to Pundit.  It will connect to
#                 servers in the mysql db and manage updates.
#
#       OPTIONS:  ---
#  REQUIREMENTS:  Perl 5.8.x
#                 Perl Modules:
#                   DBD-MySQL
#                   Net-SSH-perl
#                   perl-Math-BigInt-GMP
#                   perl-Class-ErrorHandler
#                 MySQL
#                 Public Key Auth
#                 SSH (client/server)
#                 sendmail
#                 Dependecies for above requirements not included.
#          BUGS:  ---
#         NOTES:  Search for 'edit' and replace what you need.
#        AUTHOR:  Matt Martz <mdmartz@sivel.net>
#       COMPANY:  Sivel.net
#       VERSION:  1.0rc2
#       CREATED:  Mon Nov 26 13:06:38 EST 2007
#		UPDATED:  Thu May 21 12:22:40 EST 2009
#      REVISION:  ---
#===============================================================================

use strict;
use Mysql;
use Net::SSH::Perl;
use Date::Format;

# Setup path to pundit (edit this)
my $path = "/var/www/pundit";

&log('start');

# Setup user to SSH as (edit this)
my $user = "root";

# Make DB connection. Calls db_connect function
my $dbh = db_connect();

# Query the database to list all servers
my $servers = $dbh->query("SELECT * FROM server");

# Loop results from query
while (my @server = $servers->fetchrow()) {
	
	# Set variables from row array
	my $id = $server[0];
	my $hostname = $server[1];
	my $packages = $server[2];
	my $update = $server[3];
	my $disabled = $server[7];
	my $reboot = $server[9];

	# If server is not disabled
	if ($disabled == 0) {
		
		# Build SSH Parameters
		my $ssh = eval { Net::SSH::Perl->new(
					$hostname, 
					protocol => '2',
					port => '22',
					use_pty => 1,
		) };

		if ($@) {
			&log('ERROR: connect: ' . $hostname);
			next; 
		}

		# Connect and login
		eval { $ssh->login($user) };

		if ($@) {
			&log('ERROR: auth: ' . $hostname);
			next;
		}

		# If updates scheduled and updates available run updates
		if (($update == 1) && (length($packages) > 4)) {

			my($updstdout, $updstderr, $updexit) = '';
			&log('update: ' . $hostname);
			
			# If reboot scheduled after update.  Update then reboot
			if ($reboot == 1) {
				($updstdout, $updstderr, $updexit) = $ssh->cmd('yum -y install ' . $packages);
				if (($updexit == 0) && (!$updstderr)) {
					&log('reboot: ' . $hostname);
					$ssh->cmd('reboot');
				}
			# If reboot not scheduled.  Update
			} else {
				($updstdout, $updstderr, $updexit) = $ssh->cmd('yum -y install ' . $packages);
			}

			# Get rid of extra characters trailing
			if ((length($updstdout) > 1) || (length($updstderr) > 1) || (length($updexit) > 1)) {
				chomp($updstdout);
				chomp($updstderr);
				chomp($updexit);
			}

			# If successfully executed 
			if (($updexit == 0) && (!$updstderr)) {
				my $upd_upd = $dbh->query('UPDATE server SET run_update=0 WHERE id=' . $id);
				my $upd_reboot = $dbh->query('UPDATE server SET reboot=0 WHERE id=' . $id);
				my $upd_pkg = $dbh->query('UPDATE server SET packages=\'\' WHERE id=' . $id);
	                        my $upd_err = $dbh->query('UPDATE server SET exit_stat=\'0\' WHERE id=' . $id);
	                        my $upd_err_msg = $dbh->query('UPDATE server SET error_msg=\'\' WHERE id=' . $id);
				my $upd_attempt = $dbh->query('UPDATE server SET last_update=NOW() WHERE id=' . $id);
			# If there was a failure
			} else {
				&log('ERROR: update: ' . $hostname . ': ' . $updstderr);
				&mail('Pundit Update Error', 'Error Updating ' . $hostname . "\n\nThe error was: " . $updstderr);
				my $upd_err = $dbh->query('UPDATE server SET exit_stat=\'1\' WHERE id=' . $id);
				my $upd_err_msg = $dbh->query('UPDATE server SET error_msg=`' . $updstderr . '` WHERE id=' . $id);
				my $upd_attempt = $dbh->query('UPDATE server SET last_update=NOW() WHERE id=' . $id);
			}
		
		# If updates not scheduled
		} else {

			my($chkstdout, $chkstderr, $chkexit) = '';
			&log('check-update: ' . $hostname);

			# Check for updates
			($chkstdout, $chkstderr, $chkexit) = $ssh->cmd('for pkg in $(yum check-update | egrep -i ".(i386|i486|i586|i686|x86_64|noarch)" | awk \'{print $1}\'); do echo -n $pkg" "; done;');

			# Init var if not already init'd
			if (!$chkstdout) {
				$chkstdout = '';
			}
			if (length($chkstdout) > 1) {
				chop($chkstdout);
			}

			# if successfully executed
			if (($chkexit == 0) && (!$chkstderr)) {
	                        my $upd_pkg = $dbh->query('UPDATE server SET packages=\'' . $chkstdout  . '\' WHERE id=' . $id);
	                        my $upd_err = $dbh->query('UPDATE server SET exit_stat=\'0\' WHERE id=' . $id);
	                        my $upd_err_msg = $dbh->query('UPDATE server SET error_msg=\'\' WHERE id=' . $id);
	                        my $upd_attempt = $dbh->query('UPDATE server SET last_check=NOW() WHERE id=' . $id);
			# If there was a failure
	                } else {
				&log('ERROR: check-update: ' . $hostname . ': ' . $chkstderr);
				&mail('Pundit Check Error', 'Error Checking ' . $hostname . "\n\nThe error was: " . $chkstderr);
	                        my $upd_err = $dbh->query('UPDATE server SET exit_stat=\'1\' WHERE id=' . $id);
	                        my $upd_err_msg = $dbh->query('UPDATE server SET error_msg=\'' . $chkstderr . '\' WHERE id=' . $id);
	                        my $upd_attempt = $dbh->query('UPDATE server SET last_check=NOW() WHERE id=' . $id);
	                }
		}
	}	

}

$dbh->DESTROY;

&log('end');


#===  FUNCTION  ================================================================
#          NAME:  db_connect
#   DESCRIPTION:  Creates connection to database
#    PARAMETERS:  ---
#       RETURNS:  $dbh
#===============================================================================
sub db_connect {
	# define variables for connection (edit these)
        my $dbh;
        my $db_host = 'localhost';
        my $db_name = 'pundit';
        my $db_user = 'pundit_user';
        my $db_pass = 'password';

	# connect to the db
        $dbh = eval { Mysql->connect(
                $db_host,
                $db_name,
                $db_user,
                $db_pass
        ) };

		if ($@) {
			&log('ERROR: mysql: could not connect to ' . $db_host );
			&log('end');
			die;
		}

        return $dbh;
}

#===  FUNCTION  ================================================================
#          NAME:  log
#   DESCRIPTION:  Logs parameter to file
#    PARAMETERS:  Log Text
#       RETURNS:  ---
#===============================================================================
sub log {
	my $timestamp = time2str("%b %d %T", time);
	# Open and write to log file
	open (LOGFILE, '>>' . $path . '/pundit.log');
	print LOGFILE $timestamp . " " . $_[0] . "\n";
	close (LOGFILE);
}

#===  FUNCTION  ================================================================
#          NAME:  mail
#   DESCRIPTION:  Sends mail via sendmail
#    PARAMETERS:  subject, body
#       RETURNS:  ---
#===============================================================================
sub mail {
	# Configure who the email is to be sent from (edit this)
	my $from = 'pundit@example.org';
	my $subject = $_[0];

	# Query DB for user accounts who wish to recieve alerts
	my $users = $dbh->query("SELECT * FROM user WHERE getemail=1 and disabled=0");

	# Loop through results of query
	while (my @user = $users->fetchrow()) {

		# Set variables from row array
	        my $username = $user[1];
        	my $name = $user[2];
	        my $emailaddr = $user[3];
		
		# Generate the salutation
		my $sal;
		# If user has entered their name
		if (length($name) > 0) {
			$sal = $name;
		# Else use their username
		} else {
			$sal = $username;
		}

		# Import body and set email addr
		my $body = $_[1];
		my $to = $emailaddr;

		# Build and send the email (we are using sendmail here)
		open(MAIL, "|/usr/sbin/sendmail -t") or &log('ERROR: mail: could not open sendmail');
		print MAIL "To: $to\n";
		print MAIL "From: $from\n";
		print MAIL "Subject: $subject\n";
		print MAIL "Content-type: text/plain\n\n";
		print MAIL "$sal,\n\n";
		print MAIL "$body\n";
		close(MAIL) or &log('ERROR: mail: could not send mail to ' . $to);
	}
}
